package cz.nkp.differ.user;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import cz.nkp.differ.dao.UserDAO;
import cz.nkp.differ.dao.UserDAOImpl;
import cz.nkp.differ.exceptions.UserDifferException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.log4j.Logger;
import cz.nkp.differ.model.User;
import cz.nkp.differ.util.GeneralMacros;
import org.apache.commons.codec.binary.Base64;
/**
* Class for application wide user authentication.
* @author Joshua Mabrey
* May 4, 2012
*/
public class UserManager {
private static Logger LOGGER = Logger.getLogger(UserManager.class);
private static UserManager _instance = null;
private static final String PASSWORD_HASH_ALGORITHM_NAME = "SHA-1";
private static String currentUser = null;
protected UserDAO userDAO;
protected MessageDigest passwordHashDigest;
public UserManager() {
init();
}
public UserManager(UserDAO userDAO) {
init();
this.userDAO = userDAO;
}
protected final void init() {
try {
this.passwordHashDigest = MessageDigest.getInstance(PASSWORD_HASH_ALGORITHM_NAME);
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Unable to create MessageDigest. Algorithm: " + PASSWORD_HASH_ALGORITHM_NAME);
}
}
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public static UserManager getInstance() {
if (_instance == null) {
MysqlDataSource ds = new MysqlDataSource();
ds.setUser("differ");
ds.setPassword("differ");
ds.setServerName("localhost");
ds.setDatabaseName("differ");
_instance = new UserManager(new UserDAOImpl(ds));
}
return _instance;
}
/**
* Checks the supplied username and password against the user database.
*
* TODO:Implement
* @param username
* @param userSuppliedPassword
* @return
*/
public User attemptLogin(String username, String userSuppliedPassword) throws UserDifferException {
User user = userDAO.getUserByUserName(username);
if (user == null) {
throw new UserDifferException(UserDifferException.ErrorCode.BAD_PASSWORD_OR_USERNAME, "User does not exists.");
}
String userSuppliedPasswordHash = getHashedPassword(userSuppliedPassword.toCharArray(), decode(user.getPasswordSalt()).getBytes());
if (user.getPasswordHash() == null) {
throw new NullPointerException("user.getPasswordHash() is null");
}
if (decode(user.getPasswordHash()).equals(userSuppliedPasswordHash)) {
currentUser = username;
LOGGER.trace("Logged in user: " + currentUser);
return user;
}
throw new UserDifferException(UserDifferException.ErrorCode.BAD_PASSWORD_OR_USERNAME, "Bad password.");
}
// FIXME:TODO
public synchronized User attemptAnonymousLogin() {
return null;
}
public synchronized String getLoggedInUser() {
if (currentUser == null) {
LOGGER.warn("No user logged in!");
}
return currentUser;
}
public synchronized User registerUser(User user, String passwordPlaintext) throws UserDifferException {
System.err.println("Registering new user, username: "+ user.getUserName() + " password: " + passwordPlaintext);
if (GeneralMacros.containsNull(user.getUserName(), passwordPlaintext)) {
LOGGER.debug("Null username or password!");
throw new NullPointerException("Null username or password!");
}
String salt = getPasswordSalt();
String hashedPassword = getHashedPassword(passwordPlaintext.toCharArray(), salt.getBytes());
if (hashedPassword == null) {
throw new NullPointerException("hashedPassword is null!!!");
}
user.setPasswordHash(encode(hashedPassword));
user.setPasswordSalt(encode(salt));
userDAO.addUser(user);
return user;
}
protected static String encode(String str) {
return new String(Base64.encodeBase64(str.getBytes()));
}
protected static String decode(String str) {
return new String(Base64.decodeBase64(str.getBytes()));
}
private String getPasswordSalt() {
try {
SecureRandom saltGen = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[64];
saltGen.nextBytes(salt);
return StringUtils.newStringUtf8(salt);
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Unable to find SHA1PRNG provider to generate salts.");
e.printStackTrace();
//TODO:Implement fallback
}
return null;
}
/**
* Returns a byte array representing the hash of the given salted password.
* @param saltedPassword
* @return
*/
private String getHashedPassword(char[] plaintextPassword, byte[] salt) {
if (GeneralMacros.containsNull(passwordHashDigest, salt, plaintextPassword)) {
throw new NullPointerException("Failed to hash password becuase of null arguments.");
}
try {
KeySpec spec = new PBEKeySpec(plaintextPassword, salt, 2048, 160);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] bytes = f.generateSecret(spec).getEncoded();
return StringUtils.newStringUtf8(bytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to perform hash because algorithm is missing.", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("Unable to perform hash.", e);
}
}
}